﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace NeturalMath.Expressions
{
    public class ExpressionCloningVisitor:MathExpressionVisitor<MathExpression>
    {
        public ExpressionCloningVisitor(MathRuntime runtime)
            : base(runtime)
        {
        }

        protected override MathExpression VisitAssignment(AssignmentExpression expression)
        {
            return new AssignmentExpression((LookupExpression)Visit(expression.Target), (ValueExpression) Visit(expression.Assignment), Runtime);
        }

        protected override MathExpression VisitBinaryOperator(BinaryOperatorExpression expression)
        {
            var left = (ValueExpression)Visit(expression.Left);
            var right = (ValueExpression)Visit(expression.Right);

            switch (expression.OperatorType)
            {
                case BinaryOperatorType.Add:
                    return new AddOperation(left, right,Runtime);
                case BinaryOperatorType.Subtract:
                    return new SubtractOperation(left, right, Runtime);
                case BinaryOperatorType.Multiply:
                    return new MultiplyOperation(left, right, Runtime);
                case BinaryOperatorType.Divide:
                    return new DivideOperation(left, right, Runtime);
                case BinaryOperatorType.Modulo:
                    return new ModuloOperation(left, right, Runtime);
                case BinaryOperatorType.Power:
                    return new PowerOperation(left, right, Runtime);
                case BinaryOperatorType.And:
                    return new AndOperation(left, right, Runtime);
                case BinaryOperatorType.Or:
                    return new OrOperation(left, right, Runtime);
                case BinaryOperatorType.Xor:
                    return new XorOperation(left, right, Runtime);
                case BinaryOperatorType.Equal:
                    return new EqualsOperation(left, right, Runtime);
                case BinaryOperatorType.NotEqual:
                    return new NotEqualsOperation(left, right, Runtime);
                case BinaryOperatorType.GreaterThan:
                    return new GreaterThanOperation(left, right, Runtime);
                case BinaryOperatorType.GreaterThanOrEqual:
                    return new GreaterThanOrEqualsOperation(left, right, Runtime);
                case BinaryOperatorType.LessThan:
                    return new LessThanOperation(left, right, Runtime);
                case BinaryOperatorType.LessThanOrEqual:
                    return new LessThanOrEqualsOperation(left, right, Runtime);
                case BinaryOperatorType.Range:
                    return new CreateRangeExpression(left, right, Runtime);
                case BinaryOperatorType.Unit:
                    return new UnitOperatorExpression(left, ((UnitOperatorExpression) expression).Unit, Runtime);
            }
            throw new NotImplementedException();
        }

        protected override MathExpression VisitConstant(ConstantValueExpression expression)
        {
            return new ConstantValueExpression(expression.Value, Runtime);
        }

        protected override MathExpression VisitDomain(DomainExpression expression)
        {
            return new DomainExpression(expression.Expressions.Select(Visit), Runtime);
        }

        protected override MathExpression VisitFunctionCall(FunctionUseExpression expression)
        {
            return new FunctionUseExpression(expression.MemberName, expression.Parameters.Select(Visit), Runtime);
        }

        protected override MathExpression VisitImportKeyword(ImportKeywordExpression expression)
        {
            return new ImportKeywordExpression(expression.ImportPath, Runtime);
        }

        protected override MathExpression VisitMemberLookup(LookupExpression expression)
        {
            var scoped = expression as AccessModifierExpression;
            if (scoped != null)
                return new AccessModifierExpression((LookupExpression)Visit(scoped.Lookup), scoped.Scope, Runtime);

            var function = expression as FunctionUseExpression;
            if (function != null)
                return new FunctionUseExpression(function.MemberName, function.Parameters.Select(Visit), Runtime);

            return new LookupExpression(expression.MemberName, Runtime);
        }

        protected override MathExpression VisitDefKeyword(DefKeywordExpression expression)
        {
            return new DefKeywordExpression(expression.Expressions.Select(Visit), Runtime);
        }

        protected override MathExpression VisitPrintKeyword(PrintKeywordExpression expression)
        {
            return new PrintKeywordExpression(expression.Expressions.Select(Visit), Runtime);
        }

        protected override MathExpression VisitParameterExpression(FunctionParameterExpression expression)
        {
            var value = Visit(expression.DefaultValue);
            return new FunctionParameterExpression(expression.Name, (ValueExpression)value, Runtime);
        }

        protected override MathExpression VisitUnaryOperator(UnaryOperatorExpression expression)
        {
            var inner = (ValueExpression)Visit(expression);
            switch(expression.OperatorType)
            {
                case UnaryOperatorType.Factorial:
                    return new FactorialExpression(inner, Runtime);
                case UnaryOperatorType.Not:
                    return new NotOperatorExpression(inner, Runtime);
                case UnaryOperatorType.UnaryMinus:
                    return new UnaryMinusExpression(inner, Runtime);
            }
            throw new NotImplementedException();
        }

        protected override MathExpression VisitDelegate(DelegateExpression expression)
        {
            return new DelegateExpression(expression.Value, Runtime);
        }

        protected override MathExpression VisitSymbolicLookup(SymbolicLookupExpression expression)
        {
            return new SymbolicLookupExpression(expression.MemberName, Runtime);
        }
    }
}
